spring security验证流程:
- spring security的登录验证是由UsernamePasswordAuthenticationFilter这个过滤器完成,在该类的父类AbstractAuthenticationProcessingFilter(585行)中有一个AuthenticationManager接口属性,验证工作主要是通过这个AuthenticationManager接口的实例来完成的。默认情况下,spring security会把ProviderManager类的实例注入到该属性。而自定义AbstractAuthenticationProcessingFilter的情况下,一般会在配置文件:
,起别名,然后注入该自定义类的某个属性中,看上面那个配置文件就知道了 - UsernamePasswordAuthenticationFilter的验证过程如下:
- 首先过滤器会调用自身的attemptAuthentication方法(646行),该方法规定请求必须为post,并从request中取出authentication,authentication是在SecurityContextPersistenceFilter过滤器中通过捕获用户提交的登录表单中的内容生成的一个Authentication接口实例。
- attemptAuthentication方法拿到authentication对象后,在方法中又调用ProviderManager类的authenticate方法并传入authentication对象(由于只能验证用户名、密码的简单信息,所以经常重写以实现复杂的用户验证,并返回包含当前用户所有信息的authentication),验证用户是否能登录,并处理定义登录成败重定向的页面
- 也就是说attemptAuthentication方法会调用类中的List
providers集合中各个AuthenticationProvider接口实现类中的authenticate(Authentication authentication)进行验证 - provider的实现类在验证用户时,会调用UserDetailsService的实现类的loadUserByUsername方法获取用户信息。
- 由此可见,真正的验证逻辑是由各个AuthenticationProvider接口实现类来完成的。DaoAuthenticationProvider类是默认情况下会被注入AuthenticationProvider的接口实现类。
源码分析
AbstractAuthenticationProcessingFilter
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware { // 过滤器doFilter方法 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; /* * 1、判断当前filter是否可以处理当前请求,若不行,则交给下一个filter去处理。 */ if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return; } if (logger.isDebugEnabled()) { logger.debug("Request is to process authentication"); } Authentication authResult; /* * 2、调用子类(UsernamePasswordAuthenticationFilter)attemptAuthentication方法 */ try { authResult = attemptAuthentication(request, response); if (authResult == null) { // return immediately as subclass has indicated that it hasn't completed // authentication return; } /* * 3、最终认证成功后,会处理一些与session相关的方法 * (比如将认证信息存到session等操作)。 */ sessionStrategy.onAuthentication(authResult, request, response); } /* * 3、认证失败后的一些处理。 */ catch (InternalAuthenticationServiceException failed) { logger.error( "An internal error occurred while trying to authenticate the user.", failed); unsuccessfulAuthentication(request, response, failed); return; } catch (AuthenticationException failed) { unsuccessfulAuthentication(request, response, failed); return; } /* * 4、认证成功,继续下个请求 */ if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } /* * 5、最终认证成功后的相关回调方法,主要将当前的认证信息放到SecurityContextHolder中 * 并调用认证成功处理器AuthenticationSuccessHandler接口实现类的 * onAuthenticationSuccess()。 */ successfulAuthentication(request, response, chain, authResult); } }
UsernamePasswordAuthenticaionFilter,以上AbstractAuthenticationProcessingFilter的子类,主要是重写attemptAuthentication(),规定请求必须为post。
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { //实现父类的方法 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 认证请求的方式必须为POST if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } // 获取表单参数 String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); /* * 封装进Authentication实现类UsernamePasswordAuthenticationToken * public UsernamePasswordAuthenticationToken(Object principal, Object credentials) { super((Collection)null); //设置权限为null this.principal = principal; this.credentials = credentials; this.setAuthenticated(false); } */ UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); //一般来说principle是User对象较常见 setDetails(request, authRequest); //Allow subclasses to set the "details" property //调用AuthenticationManager管理下的AuthenticationProvider接口实现类的authenticate() return this.getAuthenticationManager().authenticate(authRequest); } }
- AuthenticationManager:AuthenticationManager仅仅是一个接口,默认实现类是ProviderManager,ProviderManager不是自己直接对请求进行验证,而是将其委派给一个AuthenticationProvider列表。例如我们要同时使用jdbc认证和ldap认证,那么就可以用这个列表。列表中的每一个AuthenticationProvider将会被一次查询是否需要通过器进行验证。每个provider的验证结果只有两种情况:抛出ProviderNotFoundException异常或者完全填充一个Authentication对象,并存储在SecurityContext中。
ProviderManager
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class toTest = authentication.getClass(); Object lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); // 拿到全部的provider Iterator e = this.getProviders().iterator(); // 遍历provider while(e.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider)e.next(); // 调用provider的supports()校验是否支持当前token if(provider.supports(toTest)) { if(debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { /* * 找到后直接break,并由当前provider来进行校验工作 * 调用AuthenticationProvider实现类的authenticate() */ result = provider.authenticate(authentication); if(result != null) { this.copyDetails(authentication, result); break; } } catch (AccountStatusException var11) { this.prepareException(var11, authentication); throw var11; } catch (InternalAuthenticationServiceException var12) { this.prepareException(var12, authentication); throw var12; } catch (AuthenticationException var13) { lastException = var13; } } } // 若没有一个支持,则尝试交给父类来执行 if(result == null && this.parent != null) { try { result = this.parent.authenticate(authentication); } catch (ProviderNotFoundException var9) { ; } catch (AuthenticationException var10) { lastException = var10; } } .......................... }
DaoAuthenticationProvider:AuthenticationProvider默认实现类,实现了父类主要方法authenticate()要用到的查找用户方法retrieveUser()
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { //重现用户的方法 protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { UserDetails loadedUser; try { /* * 调用UserDetailsService接口的loadUserByUsername方法。 */ loadedUser = this.getUserDetailsService().loadUserByUsername(username); } catch (UsernameNotFoundException var6) { if(authentication.getCredentials() != null) { String presentedPassword = authentication.getCredentials().toString(); this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, (Object)null); } throw var6; } catch (Exception var7) { throw new InternalAuthenticationServiceException(var7.getMessage(), var7); } if(loadedUser == null) { throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation"); } else { return loadedUser; } } }
AbstractUserDetailsAuthenticationProvider:以上DaoAuthenticationProvider的父类,定义了authenticate(),调用子类retrieveUser()查找用户all信息,可以从db查找,并进行用户验证。
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware { // 用户验证方法 public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider .onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); String username = authentication.getPrincipal() == null? "NONE_PROVIDED":authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if(user == null) { cacheWasUsed = false; try { // 调用子类retrieveUser() user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); } catch (UsernameNotFoundException var6) { this.logger.debug("User \'" + username + "\' not found"); if(this.hideUserNotFoundExceptions) { throw new BadCredentialsException(this.messages .getMessage("AbstractUserDetailsAuthenticationProvider .badCredentials", "Bad credentials")); } throw var6; } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); } try { /* * 前检查由DefaultPreAuthenticationChecks类实现 * (主要判断当前用户是否锁定,有效,过期) */ this.preAuthenticationChecks.check(user); // 子类具体实现 this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); } catch (AuthenticationException var7) { if(!cacheWasUsed) { throw var7; } cacheWasUsed = false; user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); this.preAuthenticationChecks.check(user); this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); } // 检测用户密码是否过期 this.postAuthenticationChecks.check(user); if(!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if(this.forcePrincipalAsString) { principalToReturn = user.getUsername(); } //进行授权成功,主要是将认证信息的一块内容放到Authentication对象中返回 return this.createSuccessAuthentication(principalToReturn, authentication, user); } // 成功授权 protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { /* * 回调UsernamePasswordAuthenticationToken的构造器,这里调用的是授权成功的构造器 * public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { // 不再是null,而是传来的权限,由UserDetailsService类返回,可以从db查 super(authorities); this.principal = principal; this.credentials = credentials; // 这里是true,不再是false。 super.setAuthenticated(true); } UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities())); // 将认证信息的一块内容放到details result.setDetails(authentication.getDetails()); return result; } }
- 用户验证走完后回到起点,AbstractAuthenticationProcessingFilter.doFilter(),执行session存储和认证成功处理器。
- 总结:类之间的调用顺序
- UsernamePasswordAuthenticationFilter
- Authentication
- AuthenticationManager
- AuthenticationProvider
- UserDetailsService
- AuthenticationProvider
- UsernamePasswordAuthenticationFilter回到起点进行后续操作
- UsernamePasswordAuthenticationFilter
- 用户验证调用链
自定义用户验证
- 我们要做的工作主要是实现几个接口:用户信息UserDetails、用户信息获取服务UserDetailsService(自定义用户获取方式是从数据库还是别的地方)、而需要自定义验证方式(不只用户名、密码,增加验证码等)的时候还要实现验证工具AuthenticationProvider。
- 否则默认使用authenticate方法来验证,只能验证username和密码,不够灵活
不是通过java注册spring security的话要添加以下配置
<!--还要在http中添加--> <security:custom-filter ref="passcard_filter" after="SECURITY_CONTEXT_FILTER"/> <!--注入authentication-provider--> <security:authentication-manager alias="authenticationManager"> //起别名,方便被下面filter的bean注入 <!-- 注意,这里仅仅是系统默认的认证机制,请在正式系统中明确知道其功能再使用 --> <security:authentication-provider ref="acocunt_defaultAnthentiactionProvider"/> //默认的 <security:authentication-provider ref="registrationService"/> //注册的 <security:authentication-provider ref="enrollmentService"/> //不知干嘛的 <security:authentication-provider ref="userService"/> //登录的 </security:authentication-manager> <!--注入自定义的PasscardAuthenticationProcessingFilter,获取登录时提交的表单--> <bean id="passcard_filter" class="cn.edu.jszg.cert.security.PasscardAuthenticationProcessingFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="useVerifyCode" value="true"/> <property name="failurePage" value="/portal/home/auth/"></property> <property name="config" ref="webAppConfig"/> <property name="userLogService" ref="userLogService" /> <property name="certLoginUrl" value="${cert.login.url}"/> </bean>
自定义Authentication(包含着当前登录用户的信息)实现类PassCardAuthenticationToken(默认实现类为UsernamePasswordAuthenticationToken),用于保存authentication中用得到的参数,主要是重写以下方法
/** * 凭证,用户密码 */ @Override public Object getCredentials() { return password; } /** * 当事人,登录名 用户Id */ @Override public Object getPrincipal() { return userID; }
- Authentication类包含
- principle:其实就是实现了UserDetails的User类,它包含User类的所有属性,User中的字段和表单匹配的就会被赋值
- details:包含remoteAddress(ip地址),sessionId
- …
Authentication来源
Authentication auth=SecurityContextHolder.getContext().getAuthentication();
- spring security获取(所有)登录用户的信息
需要UserDetails实现类User增加一些支持authenticate()的方法
/*首先增加一些支持authenticate()的方法*/ /** * 返回用户所属权限 */ @Override public Collection<GrantedAuthority> getAuthorities() { return this.accesses; } @Override public Object getCredentials() { return null; } @Override public Object getDetails() { return null; } /** * 登录名称 */ @Override public Object getPrincipal() { return loginName; } /** * 是否已认证 */ @Override public boolean isAuthenticated() { return this.authenticated; } /** * 设置是否已认证字段 */ @Override public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { this.authenticated=isAuthenticated; }
创建AuthenticationProvider实现类,重写authenticate()。如果只是简单的用户名、密码比对的话,那有用户服务就够了,不需要这一步。
@Component public class MyAuthenticationProvider implements AuthenticationProvider { @Autowired private MyUserDetailsService userService; //如果重定义了可以用这个 /** * 自定义的用户验证:判断该用户是否能登录,是则返回包含所有信息的user对象,否则返回null */ @SuppressWarnings("unchecked") @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { //自定义类,继承自Authentication相同父类,用于保存authentication中用得到的参数 PassCardAuthenticationToken token=(PassCardAuthenticationToken)authentication; if(token.getUserID()!=null&&token.getPassword()!=null){ //否则直接返回null,登录失败 /* userID:当前用户名作id * 根据用户id查询数据库找到整个user信息 * 这里进行逻辑认证操作,可以获取token中的属性来自定义验证逻辑 */ User user=(User)this.getDao().executeQueryUnique("User.loadByLoginName", QueryCmdType.QUERY_NAME, token.getUserID()); /*当然最好是用重定义的UserDetailsService User user=userService.loadUserByUsername(username)*/ String password=token.getPassword(); //用户登录上传的密码 if(this.passwordEncoder!=null){ //将用户提交的密码加密后再与数据库比对 //在第2点那里加了自定义加密器之后这里就不需要再加密 password=this.passwordEncoder.encodePassword(password, null); } if(!password.equalsIgnoreCase(user.getPassword())){ token.setErrCode("2"); return null; } //如果开启了额外的验证方式的话,进行验证,否则直接判为登录成功 //不过验证码之类的一般是用前端js当场解决,不会传到后台来解决 if( token.isEnablePasscard() && usePassCard ){ //token中激活密码卡且系统使用密码卡 int position1=((token.getRow1()-1)*7)+token.getColumn1(); int position2=((token.getRow2()-1)*7)+token.getColumn2(); if(user.getPassCardId()==null){ token.setErrCode("10"); return null; } PassCard passcard=this.passCardDao.findById(user.getPassCardId(), false); if(passcard==null||passcard.getStatus()==PassCardHelper.STATUS_CANCEL ){ token.setErrCode("10"); return null; } if(passcard.getConfusedContent()==null || passcard.getConfusedContent().length()<7*7*32 ){ token.setErrCode("10"); return null; } String content=passcard.getConfusedContent(); int perLen=content.length()/49; String str1=content.substring((position1-1)*perLen, position1*perLen); String str2=content.substring((position2-1)*perLen, position2*perLen); String inputStr1=token.getCard1(); String inputStr2=token.getCard2(); if(this.passwordEncoder!=null){ inputStr1 = md5.getMD5ofStr(md5.getMD5ofStr(inputStr1)); inputStr2 = md5.getMD5ofStr(md5.getMD5ofStr(inputStr2)); } if((!str1.equalsIgnoreCase(inputStr1))||(!str2.equalsIgnoreCase(inputStr2))){ token.setErrCode("10"); return null; } } //可登陆情况 user.setLastIp(token.getIp()); user.setLastLogin(new Date()); this.getDao().saveOrUpdate(user); //更新用户的最近登录时间和ip地址 user.setAuthenticated(true); //设置为已认证,表示可登陆 /* * 导入当前用户角色,并且把权限set到User中, * 用于spring验证用户权限(getAuthorities方法) */ List<UserRole> userRoles=(List<UserRole>)this.getDao(). executeQueryList("UserRole.listRoleByUserID", QueryCmdType.QUERY_NAME, -1, -1, user.getId()); Set<GrantedAuthority> accesses=new HashSet<GrantedAuthority>(); for(UserRole ur:userRoles){ accesses.add(ur.getRole()); } user.getOrg().getOrgName(); if(user.getOrg().getCertTypes()!=null) user.getOrg().getCertTypes().size();//延迟载入一下 user.setAccesses(accesses); //导入角色 return user; } return null; } /** * 如果此处验证不通过,是不会执行authentication方法的 */ @Override public boolean supports(Class<? extends Object> authentication) { // AuthenticationProvider内置实现类只支持UsernamePasswordAuthenticationToken return authentication.equals(PassCardAuthenticationToken.class); } }
定义AbstractAuthenticationProcessingFilter实现类,重写attempAuthentication(),用于获取在登录时提交的参数,否则默认只获取j_username,j_password
import java.io.IOException; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.log4j.Logger; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.util.StringUtils; import cn.edu.jszg.cert.user.UserLog; import cn.edu.jszg.cert.user.UserLogService; import cn.edu.jszg.cert.web.WebApplicationConfiguration; import cn.edu.jszg.cert.web.controller.portal.auth.RemoteDataValidator; import com.google.code.kaptcha.servlet.KaptchaServlet; public class PasscardAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { private String successPage = "/home/admin/index"; private String failurePage = "/public/adminLoginEntry"; private boolean forward = false; private boolean useVerifyCode=true; private String certLoginUrl; static Logger logger = Logger.getLogger(PasscardAuthenticationProcessingFilter.class); private WebApplicationConfiguration config; private UserLogService userLogService; public void setConfig(WebApplicationConfiguration config) { this.config = config; } /** * 实现AbstractAuthenticationProcessingFilter的有参构造 * 没记错的话,相当于该filter的访问路径 */ protected PasscardAuthenticationProcessingFilter() { super("/adminLoginCheck"); //那表单提交应该是提交到这里 } public void setUseVerifyCode(boolean useVerifyCode) { this.useVerifyCode = useVerifyCode; } public void setUserLogService(UserLogService userLogService) { this.userLogService = userLogService; } public boolean validate(HttpServletRequest request) { String userId = request.getParameter("username"); String md2 = request.getParameter("m"); String l = request.getParameter("l"); if (userId == null || md2 == null || l == null) { return false; } long longTime = Long.parseLong(l); if (longTime < new Date().getTime()) { //获取时间秒制 return false; } try { String md1 = RemoteDataValidator.genExamMd5Digest(userId, longTime); if (md1.equals(md2)) return true; } catch (Exception e) { //e.printStackTrace(); } return false; } /** * 可以通过request获取页面传递过来的参数,并且set到相应的token中 */ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { // logger.warn("-----------------start证书登录用户----------"); HttpSession s = request.getSession(true); //Authentication子类,自动获取表单信息 PassCardAuthenticationToken token = new PassCardAuthenticationToken(); String verifyCode = request.getParameter("verifyCode"); String userID = request.getParameter("username"); //....此处省略获取参数,并且验证、赋值的逻辑 Authentication auth = null; try { //此处调用getAuthenticationManager的authenticate方法,验证用户是否能登录 auth = this.getAuthenticationManager().authenticate(token); //当supports方法返回true时执行authenticate方法 //此处为登录失败后,相应的处理逻辑 if (auth == null || !auth.isAuthenticated()) { s.setAttribute("__login_error", token.getErrCode()); } else { s.removeAttribute("__login_error"); s.removeAttribute("__login_username"); s.removeAttribute("__cert_userid"); if( token.isEnablePasscard()) { s.removeAttribute("__passcard_row1"); s.removeAttribute("__passcard_row2"); s.removeAttribute("__passcard_column1"); s.removeAttribute("__passcard_column2"); } } } catch (AuthenticationException e) { s.setAttribute("__login_error", token.getErrCode()); throw e; } return auth; } public void setSuccessPage(String successPage) { this.successPage = successPage; } public void setFailurePage(String failurePage) { this.failurePage = failurePage; } public void setForward(boolean forward) { this.forward = forward; } public void setCertLoginUrl(String certLoginUrl) { this.certLoginUrl = certLoginUrl; } @Override public void afterPropertiesSet() { super.afterPropertiesSet(); /* *该处理器实现了AuthenticationSuccessHandler, AuthenticationFailureHandler *用于处理登录成功或者失败后,跳转的界面 */ AuthenticationResultHandler handler = new AuthenticationResultHandler(); handler.setForward(forward); handler.setLoginFailurePage(failurePage); handler.setLoginSuccessPage(successPage); handler.setCertLoginUrl(certLoginUrl); //设置父类中的处理器 this.setAuthenticationSuccessHandler(handler); this.setAuthenticationFailureHandler(handler); } }
代码实例
- D:/ideaprojects/thz/thz-parent/thz-manager-web
自定义认证成功处理器
- 详解过滤链版
- 简单版
- 我们要自定义的类的被调用链:AbstractAuthenticationProcessingFilter.doFilter(调用自己的successfulAuthentication(调用AuthenticationSuccessHandler接口的onAuthenticationFailure()))
我们要做的就是自定义AuthenticationSuccessHandler实现类,重写onAuthenticationFailure(),来处理登录成功后要做的事情,比如保存用户到session、更新用户最近登录时间到数据库等。
import com.thz.pojo.User; import com.thz.service.UserService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.stereotype.Component; import javax.annotation.Resource; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Date; /** * @Author haien * @Description 自定义认证成功处理器 * 为什么不实现接口而是继承SavedRequestAwareAuthenticationSuccessHandler类, * 因为这个类记住了上次请求路径,如果你是请求其他页面被拦截到登录页的, * 这时候输入用户名和密码点击登录,还会自动跳转到该页面,而不是默认主页。 * @Date 2019/1/31 **/ @Component public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private static final Logger logger=LoggerFactory .getLogger(MyAuthenticationSuccessHandler.class); @Resource private UserService userService; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { logger.info("----登录成功,username="+request.getParameter("username")+"----"); //获取当前用户 User user=(User)authentication.getPrincipal(); //获取authentication中的details中的remoteAddress WebAuthenticationDetails wauth=(WebAuthenticationDetails)authentication.getDetails(); user=userService.findUserByName(user.getUsername()); user.setLoginTime(new Date()); user.setLoginIp(wauth.getRemoteAddress()); //存入session request.getSession().setAttribute("user",user); //更新数据库 userService.updateUser(user); //执行父类重定向到原访问路径的方法 super.onAuthenticationSuccess(request,response,authentication); } }
自定义认证失败处理器
默认处理器就只能跳转登录失败页面,自定义的话可以将用户提交的用户名保存到request,带回登录页面。
@Component("ctwAuthenticationFailureHandler") public class CtwAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{ private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private SecurityProperties securityProperties; @Override public void onAuthenticationFailure(HttpServletRequest request , HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { logger.info("登录失败!"); //根据配置loginType是否是json而定返回形式,JSON返回json if (Objects.equals(securityProperties.getBrowser().getLoginType(), LoginType.JSON)) { response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(JSON .toJSONString(new SimpleResponse(HttpStatus.INTERNAL_SERVER_ERROR.value() , exception.getMessage(), null))); } //REDIRECT跳转前面定义的页面:.failureUrl("/login.jsp?error=1") else { //存值带回 String username=request.getParameter("username"); request.setAttribute("username",username); response.setContentType("text/html;charset=UTF-8"); //设置登录失败后要重定向的url super.setDefaultFailureUrl("/login.jsp?error=1&username="+username); //重定向,request丢失 super.onAuthenticationFailure(request, response, exception); /*不然就自己转发,request就不会丢失 request.getRequestDispatcher("/login?error=1") .forward(request, response);*/ } } }
或者能确定值返回json的话,直接往客户端写json字符串
@Component public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { private static final Logger logger=LoggerFactory .getLogger(MyAuthenticationSuccessHandler.class); @Override public void onAuthenticationFailure(HttpServletRequest request , HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { logger.info("----登录失败----"); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); JSONObject result=new JSONObject(); result.put("message","用户名或密码错误"); PrintWriter printWriter=null; String jsonString=""; try { //必须捕获,不能抛出,因为要保证资源被关闭 printWriter=response.getWriter(); jsonString = JSONObject.toJSONString(result); printWriter.write(jsonString); printWriter.flush(); } catch (IOException e) { logger.error("Get response writer failed!",e); //不用再抛出,因为抛出了就要再生成一次报文,但既然getWrite都出错了生成的报文又怎么打印到前端呢 }finally { if(null!=printWriter) printWriter.close(); } } }
代码实例
- thz-manager-web/config